package schema.ext;

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.JAXBException;

import jhunter.common.exception.ConfigException;
import jhunter.context.AppContext;
import jhunter.loader.JHunterClassLoader;

import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import schema.Environment;
import schema.Node;
import schema.io.AppXmlLoader;
import schema.vo.app.App;
import schema.vo.app.ModuleResource;
import schema.vo.app.ModuleResources;
import utils.FileUtils;

public class AppExt extends Node {

	private Logger logger = LoggerFactory.getLogger(AppExt.class);

	private App app;

	private AppXmlLoader appXmlLoader = new AppXmlLoader();

	private URL classPath;

	private JHunterClassLoader classLoader;

	private Map<String, ModuleExt> moduleFileMap = new ConcurrentHashMap<String, ModuleExt>();

	private FileAlterationMonitor fileMonitor;

	private AppContext appContext;

	public void load(String path) {
		moduleFileMap.clear();
		InputStream is = setFileAndReturnStream(path);
		try {
			classPath = new File(getFile()).getParentFile().toURI().toURL();// TODO
		} catch (MalformedURLException e1) {
			throw new ConfigException(e1);
		}
		classLoader = new JHunterClassLoader(classPath);
		try {
			app = (schema.vo.app.App) appXmlLoader.getUnmarshal().unmarshal(is);

			appContext = new AppContext(this);

			ModuleResources mr = app.getModuleResources();
			if (mr != null) {
				List<ModuleResource> mlist = mr.getModuleResource();
				for (ModuleResource moduleResource : mlist) {
					ModuleExt moduleExt = new ModuleExt();
					moduleExt.setParent(this);
					moduleExt.load(moduleResource.getResource());
					this.getChildren().put(moduleExt.getContextPath(),
							moduleExt);
					moduleFileMap.put(moduleExt.getFile(), moduleExt);
				}
			}
		} catch (JAXBException e) {
			e.printStackTrace();
			throw new ConfigException(e);
		}

		setContextPath(app.getBaseContext());

		if (Environment.isDev() && fileMonitor == null) {
			fileMonitor = new FileAlterationMonitor(1000);
			File mFile = new File(getFile()).getParentFile();
			FileAlterationObserver observer = new FileAlterationObserver(mFile);
			observer.addListener(new FileAlterationListenerAdaptor() {
				@Override
				public void onFileChange(File file) {
					String path = file.getPath();
					logger.debug("file changed {}", path);
					if (FileUtils.isSameFile(path, AppExt.this.getFile())) {
						load(path);
						logger.debug("reload APP {}", AppExt.this.getApp()
								.getId());
					} else {
						boolean haveModule = moduleFileMap.containsKey(path);
						if (haveModule) {
							moduleFileMap.get(path).setNeedReload(true);
							logger.debug("reload module {}",
									moduleFileMap.get(path).getModule().getId());
						} else if (path.endsWith(".class")) {
							String className = StringUtils.substringBetween(
									path, "classes", ".class");
							className = className.replaceAll("\\\\", ".");
							className = className.replaceAll("/", ".");
							if (className.startsWith(".")) {
								className = className.substring(1);
							}
							for (ModuleExt module : moduleFileMap.values()) {
								if (module.getModuleClassSet().contains(
										className)) {
									module.setNeedReload(true);
									logger.debug("reload class {}", className);
								}
							}
						}
					}
				}
			});
			fileMonitor.addObserver(observer);
			try {
				fileMonitor.start();
			} catch (Exception e) {
				e.printStackTrace();
				throw new ConfigException(e);
			}
		}
	}

	public App getApp() {
		return app;
	}

	public void setApp(App app) {
		this.app = app;
	}

	public URL getClassPath() {
		return classPath;
	}

	public void setClassPath(URL classPath) {
		this.classPath = classPath;
	}

	public JHunterClassLoader getClassLoader() {
		return classLoader;
	}

	public JHunterClassLoader createNewClassLoader() {
		JHunterClassLoader loader = new JHunterClassLoader(classPath);
		return loader;
		// return classLoader;
	}

	public void setClassLoader(JHunterClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	public AppContext getAppContext() {
		return appContext;
	}
}
